tRPC と React Server Component
とても開発体験がよかったので、感想をメモっておく
TypeScriptファーストである点が使いやすい。とくにRSC(React Server Component)との相性が良い。GraphQLなど様々なスキーマファーストな仕様では、先にスキーマ言語で定義を書いて、各種ツールチェインで型定義を生成して、というように開発をしていくのが普通だった。しかしながらtRPCでは単にzodで定義を書いていけばそれがバリデータにもなるし型定義にもなる。ビルドスキームも単なるtypescriptのみでよい。シンプルなことはすばらしい。
もちろんサーバー側の窓口にnodeがなければこのような開発はできないが、昨今SSRなどでnodeサーバーがあるのはわりと普通になってきている。RSCを使っていればなおさらサーバーとクライアントの距離が縮まっているので、tRPCのような構成は非常に親和している。
自分が取っている構成では、基本的にgetはRSCで直でprismaなりを読みに行って、mutationだけをtRPCを経由するようにしている。RSCにするとget系では直でDBを読みに行けるので、副作用を生むmutation系の操作のためにRPCしたいだけという要求になってくる。
サービスのデザインには依ってくるとはおもうが、往々にしてmutationは少ない。そしてNext.jsでは少なからずmutationが少ない前提のデザインになっている。Next.jsでRSCを利用したときにmutationに対するUIの応答に対してはページ全体をrefetch (router.refresh) するような設計になってくる (まだRFCも出ていないので未確定な部分もあります)。全更新になってしまうので無駄のある再取得処理にはなってしまうし、optimistic updateなどインタラクティブな体験とは相性が悪くなる。これはわりと割り切った戦略だとおもうが、基本的なサービスは表示することのほうが圧倒的に多くてmutationのほうが少ないと考えると不自然でもない。
もちろん、インタラクティブなUIを構築しなければならないユースケースがあるのであれば、その部分だけClient Componentとして実装して別途に状態管理を行なって体験を良くするという手段を取れる。そのUIのためだけに部分的なdocument base cachingをするのがミニマムで良いと思う。
よくあるパターンではほぼすべてのリソースをクライアント側にデータ構造を保ちながらキャッシュしていく戦略があるとおもうが、往々にして管理しきれなくなることが多い。UIを作っているときには見えているデータ以外に興味が無くなってくるので、すべてが完璧にcache & revalidationされているかをチェックするのは非常に難しい要求だ。そこでGraphQLなどが選択肢に上がってくると思うが、そこまでして大層なdocument base cachingは必要なのだろうか?と疑問が湧いてくる。もちろんサーバー側のプロシージャで行なっているデータ変更が反映されるようにクライアント側でrevalidationを気にかけてあげれば実行可能ではあるとは思うが、ほんとに?となってしまう気持ちに嘘をつけないでいる。それと対照的に大雑把なrefetchを掛けに行くNext.jsに現実味を感じてしまって納得感が湧いてくるのである。ある意味古来のwebっぽさもあってまるでルネッサンスさながらだ。
それでいてコンポーネントベースでストリーミングされてきて、Stale-While-Revalidateの戦略で表示の更新が行われるので、ユーザーブロッキングな状態を極力減らせるようなデザインになっている。ステートレスな世界はとても単純でわかりやすい。
tRPCの話からだいぶ逸れていってしまったが、RSCのclient/serverの境目がわからなくなってくるあの感じと、typescriptの型定義が共有されたRPCは似たような雰囲気を醸し出しいて、そこもある意味あっているように思う。
しばらく使ってみてまたなにか思うところが変わってきたら感想をアップデートしていきたい。